#pragma once

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <future>
#include <functional>

/*
    基于c++11 packaged_task异步操作实现线程池
    用户通过push接口，传入要执行的任务，以及任务的参数
    push返回一个std::future对象，供用户获取对应任务执行的结果
*/

class ThreadPool
{
private:
    using Functor = std::function<void()>;
    std::atomic<bool> _stop;        // 线程终止标志
    std::vector<Functor> _taskpool; // 任务池
    std::mutex _mutex;
    std::condition_variable _cv;
    std::vector<std::thread> _threads;

private:
    // 线程的入口函数，就是从任务池中取出任务并执行
    void Entry()
    {
        while(!_stop)
        {
            std::vector<Functor> tmp_taskpool;
            {
                std::unique_lock<std::mutex> lock(_mutex);

                //判断任务池是否就绪
                _cv.wait(lock,[this](){return  _stop || !_taskpool.empty() ;});

                //将任务池的任务取出
                swap(_taskpool,tmp_taskpool);
            }

            //执行任务
            for(auto &task : tmp_taskpool)
            {
                task();
            }

        }
    }

public:
using ptr = std::shared_ptr<ThreadPool>;
    ThreadPool(int thread_count = 1)
        : _stop(false)
    {
        for (int i = 0; i < thread_count; i++)
        {
            // 循环创建线程
            _threads.emplace_back(std::thread(&ThreadPool::Entry, this));
        }
    }

    ~ThreadPool()
    {
        stop();
    }

    // 销毁线程池
    void stop()
    {
        //防止显示调用stop后调用析构二次join,造成程序异常
        if(_stop == true)  return;

        // 将线程退出标志置位true
        _stop = true;

        //唤醒所有线程
        _cv.notify_all();

        //等待线程退出
        for (auto &thread : _threads)
        {
            thread.join();
        }
    }

    /*
        1.函数需要返回一个std::future对象,但我们不知道函数的返回值，所以使用了auto, ->来显示指定返回类型 ,decltype()如果传入的是一个可调用对象，则会推导出函数返回值
        2.packaged_task需要函数签名,我们将用户传入的函数bind,生成一个void(void)的可调用对象
        3.不能直接将std::packaged_task对象构造lambda对象,会有声明周期的问题，我们使用智能指针管理std::packaged_task对象
    */

    //向任务池添加任务
    
    template<typename F,typename ...Args>
    auto push(F &&func,Args &&...args) -> std::future<decltype(func(args...))>
    {
        //1.将用户传入的函数与packaged_task绑定
        using return_type = decltype(func(args...)); 
        auto tmp_func = std::bind(std::forward<F>(func),std::forward<Args>(args)...);
        auto pack = std::make_shared<std::packaged_task<return_type()>>(tmp_func);
        std::future<return_type> fu = pack->get_future();  

        //2.构建一个lambda匿名对象，（在这个对象中执行任务）
        {   
            //3.将构建的lambda匿名对象压入任务池中
            std::unique_lock<std::mutex> lock(_mutex);

            _taskpool.push_back([pack](){
                (*pack)();
            });

            _cv.notify_one();
        }
        
        return fu;
    }   
};
